package cn.itcast.dewu.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.itcast.dewu.dto.Result;
import cn.itcast.dewu.pojo.Blog;
import cn.itcast.dewu.utils.RedisIdWork;
import cn.itcast.dewu.utils.UserHolder;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import cn.itcast.dewu.pojo.Goods;
import cn.itcast.dewu.service.GoodsService;
import cn.itcast.dewu.mapper.GoodsMapper;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static cn.itcast.dewu.utils.ESConstants.PAGE_DEFAULT_SIZE;
import static cn.itcast.dewu.utils.RedisConstants.*;

/**
 * @author Administrator
 * @description 针对表【tb_goods】的数据库操作Service实现
 * @createDate 2022-04-29 14:06:30
 */
@Service
@Slf4j
public class GoodsServiceImpl extends ServiceImpl<GoodsMapper, Goods>
        implements GoodsService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedissonClient redissonClient;


    //更新店铺
    @Override
    public Result update(Goods goods) {

        if (StringUtils.isEmpty(goods.getId())) {
            return Result.fail("商品为空,错误");
        }
        /*
        这里一定要先更新数据库,保证数据库更新完成后,才删除缓存!以保证线程安全
        * */
        //更新数据库
        boolean falg = updateById(goods);
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString(true));
        correlationData.getFuture().addCallback(
                result2 -> {
                    if (result2.isAck()) {
                        log.info("消息发送成功,消息id为:{}", correlationData.getId());
                    } else {
                        log.error("消息发送到交换机失败,消息的id为:{},发送失败的原因为:{}"
                                , correlationData.getId(), result2.getReason());
                        //重新发送消息
                        rabbitTemplate.convertAndSend("goods.saveGoods.exchange",
                                "goods.saveGoods", goods, correlationData);
                    }
                },
                ex -> {
                    log.error("消息发送异常,消息的id为:{},发送失败的原因为:{}"
                            , correlationData.getId(), ex.getMessage());
                    //重新发送消息
                    rabbitTemplate.convertAndSend("goods.saveGoods.exchange",
                            "goods.saveGoods", goods, correlationData);
                }
        );
        //发送消息
        rabbitTemplate.convertAndSend("goods.saveGoods.exchange",
                "goods.saveGoods", goods, correlationData);
        if (falg)
            //删除缓存
            stringRedisTemplate.delete(CACHE_GOODS_KEY + goods.getId());

        return Result.ok("更新成功");
    }

    @Override
    public Result queryGoodsList(Integer corrent) {
        /*
         * 先从redis 中查询*/
        String goodsJson = stringRedisTemplate.opsForValue().get("goods:list:" + corrent);
        if (!StrUtil.isEmpty(goodsJson)) {
            List<Goods> goods = JSONUtil.toList(goodsJson, Goods.class);
            return Result.ok(goods);
        }
        /*
         * 按照评分的降序
         * */
        Page<Goods> goodsPage = query().orderByDesc("is_money")
                .orderByDesc("score")
                .page(new Page<>(corrent, 6));
        //获取当前页的数据
        List<Goods> goods = goodsPage.getRecords();
        //放入缓存
        String jsonStr = JSONUtil.toJsonStr(goods);
        stringRedisTemplate.opsForValue().set("goods:list:" + corrent, jsonStr, CACHE_GOODS_TTL, TimeUnit.MINUTES);
        return Result.ok(goods);
    }

    @Resource
    private RestHighLevelClient client;

    @Override
    public Result queryGoodsByContent(Integer corrent, String content) {
        try {
            //准备请求
            SearchRequest request = new SearchRequest("goods_index");
            int from = (corrent - 1) * PAGE_DEFAULT_SIZE;
            //布尔查询
            BoolQueryBuilder booleanQuery = QueryBuilders.boolQuery();
            //搜索功能
            booleanQuery.must(QueryBuilders.matchQuery("all", content));
            //进行加分处理 ,投的钱越多越容易上热门
            FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
                    booleanQuery,//原始查询
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                            new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                    QueryBuilders.rangeQuery("isMoney").gte(0),
                                    ScoreFunctionBuilders.fieldValueFactorFunction("isMoney")
                            )
                    }
            );
            request.source().query(functionScoreQuery);
            //高亮显示
            request.source().highlighter(new HighlightBuilder()
                    .field("name").field("address").field("area")
                    .requireFieldMatch(false));
            //分页功能
            request.source().from(from).size(PAGE_DEFAULT_SIZE);
            //发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return handleResponse(response);
        } catch (Exception e) {
            throw new RuntimeException("查询失败,请重试", e);
        } finally {
               /* try {
                    if (client != null)
                    client.close();
                } catch (IOException e) {
                    e.printStackTrace();
            }*/
        }
    }

    @Override
    public Result queryMyGoods() {
        //获取当前登录id
        Long userId = UserHolder.getUser().getId();
//        先从数据库查询是否存在
        String goodsListJson = stringRedisTemplate.opsForValue().get("mygoods:list:" + userId);
        if (StrUtil.isNotBlank(goodsListJson)) {
            if (goodsListJson.equals("1")) {
                return Result.ok();
            }
            List<Goods> goods = JSONUtil.toList(goodsListJson, Goods.class);
            return Result.ok(goods);
        }
        List<Goods> goodsList = query().eq("user_id", userId).list();
        if (goodsList.isEmpty()) {
            stringRedisTemplate.opsForValue().set("mygoods:list:" + userId,
                    "1", CACHE_NULL_TTL, TimeUnit.SECONDS);
            return Result.ok();
        }
        String jsonStr = JSONUtil.toJsonStr(goodsList);
        stringRedisTemplate.opsForValue().set("mygoods:list:" + userId,
                jsonStr, CACHE_NULL_TTL, TimeUnit.SECONDS);
        return Result.ok(goodsList);
    }

    /*
     * 增加商品
     * */
    @Autowired
    private RedisIdWork idWork;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public Result saveGoods(Goods goods) {
        /*String paice = goods.getDis().toString();
        if (paice.substring(paice.indexOf(".")).length() > 2) {
            return Result.fail("请输入正确的钱数");
        }*/
        goods.setId(idWork.nextID("goods"));
        //获取当前用户id
        Long userId = UserHolder.getUser().getId();
        goods.setUserId(userId);

        boolean falg = save(goods);

        if (!falg) {
            return Result.fail("添加失败,请重试^_^");
        }
        //添加商品将库存加入到redis中
        stringRedisTemplate.opsForValue().set("goods:special:stock:" + goods.getId(),
                goods.getStock().toString());
        //发送到rabbitmq中
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString(true));
        correlationData.getFuture().addCallback(
                result2 -> {
                    if (result2.isAck()) {
                        log.info("消息发送成功,消息id为:{}", correlationData.getId());
                    } else {
                        log.error("消息发送到交换机失败,消息的id为:{},发送失败的原因为:{}"
                                , correlationData.getId(), result2.getReason());
                        //重新发送消息
                        rabbitTemplate.convertAndSend("goods.saveGoods.exchange",
                                "goods.saveGoods", goods, correlationData);
                    }
                },
                ex -> {
                    log.error("消息发送异常,消息的id为:{},发送失败的原因为:{}"
                            , correlationData.getId(), ex.getMessage());
                    //重新发送消息
                    rabbitTemplate.convertAndSend("goods.saveGoods.exchange",
                            "goods.saveGoods", goods, correlationData);
                }
        );
        //发送消息
        rabbitTemplate.convertAndSend("goods.saveGoods.exchange",
                "goods.saveGoods", goods, correlationData);
        return Result.ok("添加成功");
    }

    @Override
    public Result deleteGoodsById(Long id) {
        boolean falg = removeById(id);
        if (!falg) {
            return Result.fail("删除失败请重试");
        }
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString(true));
        correlationData.getFuture().addCallback(
                result2 -> {
                    if (result2.isAck()) {
                        log.info("消息发送成功,消息id为:{}", correlationData.getId());
                    } else {
                        log.error("消息发送到交换机失败,消息的id为:{},发送失败的原因为:{}"
                                , correlationData.getId(), result2.getReason());
                        //重新发送消息
                        rabbitTemplate.convertAndSend("goods.delete.exchange",
                                "goods.delete", id, correlationData);
                    }
                },
                ex -> {
                    log.error("消息发送异常,消息的id为:{},发送失败的原因为:{}"
                            , correlationData.getId(), ex.getMessage());
                    //重新发送消息
                    rabbitTemplate.convertAndSend("goods.delete.exchange",
                            "goods.delete", id, correlationData);
                }
        );
        //发送消息
        rabbitTemplate.convertAndSend("goods.delete.exchange",
                "goods.delete", id, correlationData);
        Boolean aBoolean = stringRedisTemplate.delete(CACHE_GOODS_KEY + id);
            return Result.ok();
    }


    private Result handleResponse(SearchResponse response) {
        //获取hits
        SearchHits hits = response.getHits();
        //总条数
        long total = hits.getTotalHits().value;
        //再次获取hits集合
        SearchHit[] hitsHits = hits.getHits();
        List<Goods> goodsList = new ArrayList<>();
        //进行遍历
        for (SearchHit hit : hitsHits) {
            //获取_source
            String goodsJson = hit.getSourceAsString();
            //进行反序列化 非高亮的
            Goods goods = JSONUtil.toBean(goodsJson, Goods.class);
            //处理高亮
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (highlightFields.isEmpty() || highlightFields != null) {
                //对内容以及标题进行高亮获取高亮结果
                HighlightField field = highlightFields.get("name");
                HighlightField field2 = highlightFields.get("address");
                HighlightField field3 = highlightFields.get("area");
                if (field != null) {
                    String hName = field.getFragments()[0].toString();
                    goods.setName(hName);
                }
                if (field2 != null) {
                    String hName = field2.getFragments()[0].toString();
                    goods.setAddress(hName);
                }
                if (field3 != null) {
                    String hName = field3.getFragments()[0].toString();
                    goods.setArea(hName);
                }
            }
            //放入到集合中
            goodsList.add(goods);
        }
        //返回总条数 以及blog的集合
        return Result.ok(goodsList, total);
    }


    /*
     * 根据id查询商品的信息
     * */
    @Override
    public Result queryGoodsByID(Long id) {
        //用于解决缓存穿透的问题
        Goods goods = queryWithMutex(id);
        if (goods == null) {
            return Result.fail("无商品^_^");
        }
        return Result.ok(goods);
    }

    private Goods queryWithMutex(Long id) {
        //先从redis中进行查询是否存在商品信息
        String goodsKey = CACHE_GOODS_KEY + id;
        Map<Object, Object> shopMap = stringRedisTemplate.opsForHash().entries(goodsKey);
        if (!shopMap.isEmpty()) {
            //缓存中存在商品信息
            Goods goods = BeanUtil.fillBeanWithMap(shopMap, new Goods(),true);
            return goods;
        }
        Goods goods = null;
        //得到锁对象
        RLock rLock = redissonClient.getLock(LOCK_GOODS_KEY + id);
        try {
            boolean lock = rLock.tryLock(1L, 10, TimeUnit.SECONDS);
            if (lock) {
                shopMap = stringRedisTemplate.opsForHash().entries(goodsKey);
                if (!shopMap.isEmpty()) {
                    //缓存中存在商品信息
                    goods = BeanUtil.fillBeanWithMap(shopMap, new Goods(), true);
                    return goods;
                }
            } else {
                Thread.sleep(50);
                this.queryWithMutex(id);
            }
            //去查询数据库
            goods = getById(id);
            if (goods == null) {
                //当数据库中没有该商品时将插入一个空值用来防止缓存穿透
                stringRedisTemplate.opsForHash().put(goodsKey,
                        "", "");
                stringRedisTemplate.expire(goodsKey, CACHE_NULL_TTL, TimeUnit.MINUTES);
                return null;
            }
            //缓存重建
            Map<String, Object> goodsMap = BeanUtil.beanToMap(goods, new HashMap<>(), CopyOptions.create()
                    .ignoreNullValue()
                    .setFieldValueEditor((fileName, fileFiled) ->
                            fileFiled.toString()
                    ));
            stringRedisTemplate.opsForHash().putAll(goodsKey, goodsMap);
            stringRedisTemplate.expire(goodsKey, CACHE_GOODS_TTL, TimeUnit.MINUTES);//设置超时时间
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //释放锁
            rLock.unlock();
        }
        return goods;
    }

}

